﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Emit;
using System.Text;
using NovoUSB2XXX;
using NovoDataBase;
using System.Windows.Forms;
using System.Xml.Linq;

namespace NovoSerDes
{
    public class SerDesComm
    {
        private int devID = -1;
        private byte currentDevAddr = 0x42 << 1;    //CH347 need a 8bits device address
        private int totalDevCnt = 0;
        private IICMode defaultIICMode = IICMode.A16D32;
        private NOVO_IIC_CONFIG i2c_cfg = new NOVO_IIC_CONFIG 
        { 
            ClockSpeedHz = 1,
            EnablePu = 0
        };
        public SerDesComm()
        {
            //this.devID = _devID; 
            int ret = NovoComm.Novo_CreateSdk();

            NOVO_DEV_CONFIG myDev = new NOVO_DEV_CONFIG();
            myDev.devType = NOVO_DEV_List.CH347;
            myDev.I2C_EN = true;
            myDev.SPI_EN = false;
            myDev.UART_EN = false;
            myDev.GPIO_EN = false;

            ret = NovoComm.Novo_InitDev(ref myDev);
            Console.WriteLine("Novo_InitDev result = {0}", ret);
            Debug.WriteLine("Test ");

            StringBuilder devName = new StringBuilder();
            NovoComm.Novo_GetDevName(devName);
            Console.WriteLine("Dev Name = {0}", devName);

            //ret = NovoComm.Novo_SetDeviceId(devID);
            //Console.WriteLine("Change Id to {0}, result ={0}", devID, ret);

            //ret = NovoComm.Novo_OpenDevice();
            //this.isOpen = ret == 0 ? true : false ;
            //Console.WriteLine("Novo_OpenDevice result ={0}", ret);

            //NOVO_DEV_INFO dEV_INFO = new NOVO_DEV_INFO();
            //ret = NovoComm.Novo_GetDevInfo(ref dEV_INFO);

            //NOVO_IIC_CONFIG i2c_cfg;
            //i2c_cfg.ClockSpeedHz = (UInt32)NOVO_IIC_Speed.IIC_100K;
            //i2c_cfg.EnablePu = 0;
            //ret = NovoComm.Novo_IIC_Init(ref i2c_cfg);
            //Console.WriteLine("Novo_IIC_Init: {0} \n", ret);
        }

        public int DevID
        {
            get { return devID; }
        }

        public int TotalDeviceCount
        {
            get { return totalDevCnt; }
        }
                
        private bool isOpen = false;
        public bool IsOpen
        { 
            get { return this.isOpen; } 
        }

        public NOVO_IIC_CONFIG I2C_CFG
        { get { return this.i2c_cfg; } }

        public string GetDevName()
        {
            StringBuilder devName = new StringBuilder();
            NovoComm.Novo_GetDevName(devName);

            return devName.ToString();
        }

        public bool OpenDevice(int _devID)
        {
            this.devID = _devID;
            int ret = NovoComm.Novo_CreateSdk();

            NOVO_DEV_CONFIG myDev = new NOVO_DEV_CONFIG();
            myDev.devType = NOVO_DEV_List.CH347;
            myDev.I2C_EN = true;
            myDev.SPI_EN = false;
            myDev.UART_EN = false;
            myDev.GPIO_EN = false;

            ret = NovoComm.Novo_InitDev(ref myDev);
            Console.WriteLine("Novo_InitDev result = {0}", ret);

            StringBuilder devName = new StringBuilder();
            NovoComm.Novo_GetDevName(devName);
            Console.WriteLine("Dev Name = {0}", devName);

            ret = NovoComm.Novo_SetDeviceId(devID);
            Console.WriteLine("Change Id to {0}, result ={0}", devID, ret);

            ret = NovoComm.Novo_OpenDevice();
            this.isOpen = ret == 0 ? true : false;
            Console.WriteLine("Novo_OpenDevice result ={0}", ret);
                        
            ret = NovoComm.Novo_IIC_Init(ref i2c_cfg);
            Console.WriteLine("Novo_IIC_Init: {0} \n", ret);

            return ret == 0 ? true : false;
        }

        public bool ReOpenDevice()
        {
            //this.devID = _devID;               
            int ret = NovoComm.Novo_CreateSdk();

            NOVO_DEV_CONFIG myDev = new NOVO_DEV_CONFIG();
            myDev.devType = NOVO_DEV_List.CH347;
            myDev.I2C_EN = true;
            myDev.SPI_EN = false;
            myDev.UART_EN = false;
            myDev.GPIO_EN = false;

            ret = NovoComm.Novo_InitDev(ref myDev);
            Console.WriteLine("Novo_InitDev result = {0}", ret);

            StringBuilder devName = new StringBuilder();
            NovoComm.Novo_GetDevName(devName);
            Console.WriteLine("Dev Name = {0}", devName);

            ret = NovoComm.Novo_SetDeviceId(devID);
            Console.WriteLine("Change Id to {0}, result ={0}", devID, ret);

            ret = NovoComm.Novo_OpenDevice();
            this.isOpen = ret == 0 ? true : false;
            Console.WriteLine("Novo_OpenDevice result ={0}", ret);

           ret = NovoComm.Novo_IIC_Init(ref i2c_cfg);
            Console.WriteLine("Novo_IIC_Init: {0} \n", ret);

            return ret == 0 ? true : false;
        }

        public bool CloseDevice() 
        {
            if (NovoComm.Novo_CloseDevice() == 0)
            {
                this.isOpen = false;
                return true;
            }
            else
            {
                return false;
            }
        }
        
        /// <summary>
        /// 7bits device address
        /// </summary>
        public byte CurrentDevAddr
        {
            set { this.currentDevAddr = (byte)(value << 1); }
            get { return (byte)(this.currentDevAddr >> 1); }
        }

        private byte[] DataToByteArray(object data, int byteCnt)
        {
            byte[] targetBuffer = new byte[byteCnt];

            if (byteCnt == 1)
            {
                targetBuffer[0] = Convert.ToByte(data);
            }
            else if (byteCnt == 2)
            {
                byte[] tempData = BitConverter.GetBytes(Convert.ToUInt16(data));
                for (int ix = 0; ix < byteCnt; ix++)
                    targetBuffer[ix] = tempData[1 - ix];
            }
            else if (byteCnt == 4)
            {
                // tempData array convert as little endian
                byte[] tempData = BitConverter.GetBytes(Convert.ToUInt32(data));
                for (int ix = 0; ix < byteCnt; ix++)
                    targetBuffer[ix] = tempData[3 - ix];
            }

            return targetBuffer;
        }

        private object ByteArrayToData(byte[] arr, int byteCnt)
        {
            
            if (byteCnt == 1)
                return (object)arr[0];

            byte[] tempArray = new byte[byteCnt];
            for (int ix = 0; ix < byteCnt; ix++)    // little endian to big endian
            {
                tempArray[ix] = arr[3 - ix];
            }

            if (byteCnt == 2)   // Just use arr[0] and arr[1], and arr[1] used first            
                return BitConverter.ToUInt16(tempArray, 2);
            else if (byteCnt == 4)
                return BitConverter.ToUInt32(tempArray, 0);
            else
                return 0;
           
        }

        #region IIC for Script
        public bool IICWrite(byte devAddr, object RegAddr, object RegData, IICMode i2cMode)
        {
            int byteCnt_RegAddr = ((byte)i2cMode >> 4) & 0x0F;
            int byteCnt_RegData = ((byte)i2cMode & 0x0F);

            byte[] wrBuffer = new byte[byteCnt_RegAddr + byteCnt_RegData + 1];
            
            wrBuffer[0] = devAddr;
            //byte[] tempBuffer = BitConverter.GetBytes((UInt32)RegAddr);
            byte[] tempBuffer = DataToByteArray(RegAddr, byteCnt_RegAddr);
            Array.Copy(tempBuffer, 0, wrBuffer, 1, byteCnt_RegAddr);

            //tempBuffer = BitConverter.GetBytes((UInt32)RegData);
            tempBuffer = DataToByteArray(RegData, byteCnt_RegData);
            Array.Copy(tempBuffer, 0, wrBuffer, byteCnt_RegAddr + 1, byteCnt_RegData);

            return (NovoComm.Novo_IIC_RW_U8_Basic(devAddr, wrBuffer, wrBuffer.Length, null, 0) == 0) ? true : false; 
        }

        public bool IICRead(byte devAddr, object RegAddr, out object RegData, IICMode i2cMode)
        {
            int byteCnt_RegAddr = ((byte)i2cMode >> 4) & 0x0F;
            int byteCnt_RegData = ((byte)i2cMode & 0x0F);

            byte[] wrBuffer = new byte[byteCnt_RegAddr + 1];
            byte[] rdBuffer = new byte[(byteCnt_RegData + 3)/4*4];  //Buffer size should be 4x for UInt32 convert.

            wrBuffer[0] = devAddr;
            //byte[] tempBuffer = BitConverter.GetBytes((UInt32)RegAddr);
            byte[] tempBuffer = DataToByteArray(RegAddr, byteCnt_RegAddr);
            Array.Copy(tempBuffer, 0, wrBuffer, 1, byteCnt_RegAddr);

            int ret = NovoComm.Novo_IIC_RW_U8_Basic(devAddr, wrBuffer, wrBuffer.Length, rdBuffer, byteCnt_RegData);

            if(ret == 0)
            {
                RegData = ByteArrayToData(rdBuffer, byteCnt_RegData);
                return true;
            }
            else
            {
                RegData = 0;
                return false;
            }           
        }

        public bool IICWrite_Burst(byte devAddr, object RegAddr, object[] RegData, IICMode i2cMode)
        {
            int byteCnt_RegAddr = ((byte)i2cMode >> 4) & 0x0F;
            int byteCnt_RegData = ((byte)i2cMode & 0x0F) * RegData.Length;
            int byteCnt_PerData = (byte)i2cMode & 0x0F;

            byte[] wrBuffer = new byte[byteCnt_RegAddr + byteCnt_RegData + 1];
            wrBuffer[0] = devAddr;

            //byte[] tempBuffer = BitConverter.GetBytes((UInt32)RegAddr);
            byte[] tempBuffer = DataToByteArray(RegAddr, byteCnt_RegAddr);
            Array.Copy(tempBuffer, 0, wrBuffer, 1, byteCnt_RegAddr);

            for (int ix = 0; ix < RegData.Length; ix++)
            {
                //tempBuffer = BitConverter.GetBytes((UInt32)RegData[ix]);
                tempBuffer = DataToByteArray(RegData[ix], byteCnt_PerData);
                Array.Copy(tempBuffer, 0, wrBuffer, 1 + byteCnt_RegAddr + ix * byteCnt_PerData, byteCnt_PerData);
            }

            return (NovoComm.Novo_IIC_RW_U8_Basic(devAddr, wrBuffer, wrBuffer.Length, null, 0) == 0) ? true : false;
        }

        public bool IICRead_Burst(byte devAddr, object RegAddr, out object[] RegData, int readCnt, IICMode i2cMode)
        {
            int byteCnt_RegAddr = ((byte)i2cMode >> 4) & 0x0F;
            int byteCnt_RegData = ((byte)i2cMode & 0x0F) * readCnt;
            int byteCnt_PerData = (byte)i2cMode & 0x0F;
            RegData = new object[readCnt];

            byte[] wrBuffer = new byte[byteCnt_RegAddr + 1];
            byte[] rdBuffer = new byte[(byteCnt_RegData + 3)/4*4]; //Buffer size should be 4x for UInt32 convert.

            wrBuffer[0] = devAddr;
            //byte[] tempBuffer = BitConverter.GetBytes((UInt32)RegAddr);
            byte[] tempBuffer = DataToByteArray(RegAddr, byteCnt_RegAddr);
            Array.Copy(tempBuffer, 0, wrBuffer, 1, byteCnt_RegAddr);

            int ret = NovoComm.Novo_IIC_RW_U8_Basic(devAddr, wrBuffer, wrBuffer.Length, rdBuffer, byteCnt_RegData);

            if (ret == 0)
            {
                for (int ix = 0; ix < readCnt; ix++)
                {
                    RegData[ix] = ByteArrayToData(rdBuffer.Skip(ix * byteCnt_PerData).Take(byteCnt_PerData).ToArray(), byteCnt_PerData);

                    //if(byteCnt_PerData == 1)
                    //    RegData[ix] = (object)rdBuffer[ix];
                    //else if(byteCnt_PerData == 2)
                    //    RegData[ix] = (object)BitConverter.ToUInt16(rdBuffer.Skip(ix * byteCnt_PerData).Take(byteCnt_PerData).ToArray(), 0);
                    //else if(byteCnt_PerData == 4)
                    //    RegData[ix] = (object)BitConverter.ToUInt32(rdBuffer.Skip(ix * byteCnt_PerData).Take(byteCnt_PerData).ToArray(), 0); 
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region I2C for Regmap UI
        public bool IICWrite(byte devAddr, UInt16 RegAddr, UInt32 RegData) 
        { return false; }

        //public bool IICWrite(UInt32 RegAddr, UInt32 RegData)
        //{ return false; }

        public bool IICWrite(UInt32 RegAddr, UInt32 RegData)
        {
            return NovoComm.Novo_IIC_Wr_U16U32(this.currentDevAddr, (UInt16)(RegAddr), RegData) == 0 ? true : false ;
        }

        public bool IICWrite(UInt32 RegAddr, UInt32 RegData, UInt32 startBit, UInt32 endBit)
        {
            //IICSetBitField RegAddr [x:y] Value; 
            // Parameter sequence: RegAddr EndBitIndex StartBitIndex BFValue
            UInt32 rdData = 0;
            UInt32 wrData = 0;
            int ret = NovoComm.Novo_IIC_Rd_U16U32(this.currentDevAddr, (UInt16)RegAddr, ref rdData);
            if (ret != 0)
            {
                return false;
            }

            Console.WriteLine(string.Format("IIC Read: Reg0x{0} = 0x{1}", RegAddr.ToString("X2"), rdData.ToString("X2")));


            UInt32 bfMask = (UInt32)(Math.Pow(2, endBit + 1) - Math.Pow(2, startBit));

            //Console.WriteLine(((1 << (int)(endBitIx + 1)) - 1).ToString("X2"));
            //Console.WriteLine(((1 << (int)startBitIx) - 1).ToString("X2"));

            //Console.WriteLine(((UInt32)(Math.Pow(2, endBitIx + 1) - 1)).ToString("X2"));
            //Console.WriteLine(((UInt32)(Math.Pow(2, startBitIx) - 1)).ToString("X2"));


            wrData = (rdData & ~bfMask) | ((RegData << (int)startBit) & bfMask);
            ret = NovoComm.Novo_IIC_Wr_U16U32(this.currentDevAddr, (UInt16)RegAddr, wrData);
            if (ret != 0)
            {
                return false;
            }
            Console.WriteLine(string.Format("IIC Write: Reg0x{0} = 0x{1}; BF Mask: 0x{2}", RegAddr.ToString("X2"), wrData.ToString("X2"), bfMask.ToString("X2")));

            return true;

        }

        public bool IICWrite_Burst(byte devAddr, UInt16 RegAddr, UInt32[] RegData, int count)
        { return false; }

        public bool IICRead(byte devAddr, UInt32 RegAddr, out UInt32 RegData)
        {
            RegData = 0;
            return false; 
        }

        public bool IICRead(UInt32 RegAddr, out UInt32 RegData)
        {
            RegData = 0;
            int ret = NovoComm.Novo_IIC_Rd_U16U32(currentDevAddr, (UInt16)(RegAddr), ref RegData);

            return (ret == 0) ? true : false; 
        }

        public bool IICRead(UInt32 RegAddr, out UInt32 RegData, UInt32 startBit, UInt32 endBit)
        {
            RegData = 0;
            //int ret = NovoComm.Novo_IIC_Rd_U16U32(currentDevAddr, (UInt16)(RegAddr), ref RegData);

            //return (ret == 0) ? true : false;

            UInt32 rdData = 0;
            UInt32 bfValue = 0;
            int ret = NovoComm.Novo_IIC_Rd_U16U32(this.currentDevAddr, (UInt16)RegAddr, ref rdData);
            if (ret != 0)
            {
                return false;
            }

            //Console.WriteLine(string.Format("IIC Read: Reg0x{0} = 0x{1}", RegAddr.ToString("X2"), rdData.ToString("X2")));

            UInt32 bfMask = (UInt32)(Math.Pow(2, endBit + 1) - Math.Pow(2, startBit));

            //Console.WriteLine(((1 << (int)(endBitIx + 1)) - 1).ToString("X2"));
            //Console.WriteLine(((1 << (int)startBitIx) - 1).ToString("X2"));

            //Console.WriteLine(((UInt32)(Math.Pow(2, endBitIx + 1) - 1)).ToString("X2"));
            //Console.WriteLine(((UInt32)(Math.Pow(2, startBitIx) - 1)).ToString("X2"));
            bfValue = (UInt32)((Convert.ToUInt32(rdData) & bfMask) >> (int)startBit);
            RegData = bfValue;

            Console.WriteLine(string.Format("IIC Read: Reg0x{0} = 0x{1}; BF Mask: 0x{2}\r\n", 
                RegAddr.ToString("X2"), rdData.ToString("X2"), bfMask.ToString("X2")));


            return true;
        }

        public bool IICRead_Burst(byte devAddr, UInt16 RegAddr, out UInt32[] RegData, int count)
        {
            RegData = new UInt32[count];
            return false;
        }

        public int IIC_ScanDevAddr(byte[] devAdd)
        {
            byte[] tempAddrs = new byte[255];
            int devCnt = NovoComm.Novo_IIC_ScanDevAddr(tempAddrs);

            for (int ix = 0; ix < devCnt; ix++)
            {
                devAdd[ix] = tempAddrs[ix];
            }

            return devCnt;
        }

        /// <summary>
        /// Set IIC Mode and Pull EN
        /// </summary>
        /// <param name="i2cMode">0-20K; 1-100K; 2-400K; 3-750K; 4-1M</param>
        /// <param name="PU_En">0: Disable; 1: Enable</param>
        /// <returns></returns>
        public bool IIC_Mode_Set(uint i2cMode, byte PU_En)
        {
            i2c_cfg.ClockSpeedHz = i2cMode;
            i2c_cfg.EnablePu = PU_En;
            int ret = NovoComm.Novo_IIC_Init(ref i2c_cfg);
            Console.WriteLine("Novo_IIC_Init: {0} \n", ret);

            return (ret == 0) ? true : false;
        }
        #endregion I2C for RegMap UI

        public int EnumDevice()
        {
            totalDevCnt = NovoComm.Novo_EnumDevice();
            return totalDevCnt;
        }

        public bool GPIO_Set(byte mask, byte data)
        {
            return (NovoComm.Novo_GPIO_Set(mask, data) == 0) ? true : false;
        }
    }

    //public enum IICMode
    //{
    //    None = 0,
    //    A8D8 = 0x11,
    //    A8D16 = 0x12, 
    //    A8D32 = 0x14,
    //    A16D8 = 0x21,
    //    A16D16 = 0x22,
    //    A16D32 = 0x24,
    //    A32D8 = 0x41,
    //    A32D16 = 0x42,
    //    A32D32 = 0x44
    //}


}
